package dao

import (
	"fmt"
	"go_gateway/public"
	"go_gateway/reverse_proyx/load_balance"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/e421083458/gorm"
	"github.com/gin-gonic/gin"
)

type LoadBalance struct {
	ID                     int64  `json:"id" gorm:"primary_key"`
	ServiceID              int64  `json:"service_id" gorm:"column:service_id" description:"服务id	"`
	CheckMethod            int    `json:"check_method" gorm:"column:check_method" description:"检查方法 tcpchk=检测端口是否握手成功	"`
	CheckTimeout           int    `json:"check_timeout" gorm:"column:check_timeout" description:"check超时时间	"`
	CheckInterval          int    `json:"check_interval" gorm:"column:check_interval" description:"检查间隔, 单位s		"`
	RoundType              int    `json:"round_type" gorm:"column:round_type" description:"轮询方式 round/weight_round/random/ip_hash"`
	IpList                 string `json:"ip_list" gorm:"column:ip_list" description:"ip列表"`
	WeightList             string `json:"weight_list" gorm:"column:weight_list" description:"权重列表"`
	ForbidList             string `json:"forbid_list" gorm:"column:forbid_list" description:"禁用ip列表"`
	UpstreamConnectTimeout int    `json:"upstream_connect_timeout" gorm:"column:upstream_connect_timeout" description:"下游建立连接超时, 单位s"`
	UpstreamHeaderTimeout  int    `json:"upstream_header_timeout" gorm:"column:upstream_header_timeout" description:"下游获取header超时, 单位s	"`
	UpstreamIdleTimeout    int    `json:"upstream_idle_timeout" gorm:"column:upstream_idle_timeout" description:"下游链接最大空闲时间, 单位s	"`
	UpstreamMaxIdle        int    `json:"upstream_max_idle" gorm:"column:upstream_max_idle" description:"下游最大空闲链接数"`
}

func (t *LoadBalance) TableName() string {
	return "gateway_service_load_balance"
}

func (t *LoadBalance) Find(c *gin.Context, tx *gorm.DB, search *LoadBalance) (*LoadBalance, error) {
	model := &LoadBalance{}
	err := tx.SetCtx(public.GetGinTraceContext(c)).Where(search).Find(&model).Error

	return model, err
}

func (t *LoadBalance) Save(c *gin.Context, tx *gorm.DB) error {
	return tx.SetCtx(public.GetTraceContext(c)).Save(t).Error
}

func (t *LoadBalance) GetIPListByModel() []string {
	return strings.Split(t.IpList, ",")
}

func (t *LoadBalance) GetWeightListByModel() []string {
	return strings.Split(t.WeightList, ",")
}

var LoadBalancerManager *LoadBalancer

type LoadBalancer struct {
	LoadBalanceMap   map[string]*LoadBalancerItem
	LoadBalanceSlice []*LoadBalancerItem
	Locker           sync.RWMutex
}

type LoadBalancerItem struct {
	LoadBalance load_balance.LoadBalance
	ServiceName string
}

func NewLoadBalancer() *LoadBalancer {
	return &LoadBalancer{LoadBalanceMap: make(map[string]*LoadBalancerItem), LoadBalanceSlice: []*LoadBalancerItem{},
		Locker: sync.RWMutex{}}
}

func init() {
	LoadBalancerManager = NewLoadBalancer()
	TransporterManager = NewTransporter()
}

// GetLoadBalancer
// @Description: 根据服务名称获取负载均衡配置
// @receiver l
// @param service
// @return load_balance.LoadBalance
// @return error
//
func (l *LoadBalancer) GetLoadBalancer(service *ServiceDetail) (load_balance.LoadBalance, error) {
	serviceName := service.Info.ServiceName
	// 1.查看map中是否已经存储该服务的配置

	for _, balanceItem := range l.LoadBalanceSlice {
		// map中已有该服务的负载均衡配置
		if balanceItem.ServiceName == serviceName {
			return balanceItem.LoadBalance, nil
		}
	}
	schema := "http://"
	if service.HTTPRule.NeedHttps == 1 {
		schema = "https://"
	}
	if service.Info.LoadType == public.LoadTypeTCP || service.Info.LoadType == public.LoadTypeGRPC {
		schema = ""
	}
	// 2.解析服务的下游服务器 ip列表 ip权重列表
	ipList := service.LoadBalance.GetIPListByModel()
	weightList := service.LoadBalance.GetIPListByModel()
	// 3.配置ipConf,
	ipConf := map[string]string{}
	for ipIndex, ipItem := range ipList {
		ipConf[ipItem] = weightList[ipIndex]
	}
	// 4.生成LoadBalance配置
	conf, err := load_balance.NewLoadBalanceCheckConf(fmt.Sprintf("%s%s", schema, "%s"), ipConf)
	if err != nil {
		return nil, err
	}
	// 5.根据配置 生成负载均衡器
	lb := load_balance.LoadBalanceFactorWithConf(load_balance.LbType(service.LoadBalance.RoundType), conf)

	// 6.将服务名称 和负载均衡器组装成item 添加到slice中
	lbItem := &LoadBalancerItem{
		LoadBalance: lb,
		ServiceName: serviceName,
	}
	l.LoadBalanceSlice = append(l.LoadBalanceSlice, lbItem)

	// 7.使用锁 保证map的线程安全
	l.Locker.Lock()
	defer l.Locker.Unlock()
	l.LoadBalanceMap[serviceName] = lbItem
	return lb, nil
}

var TransporterManager *Transporter

type Transporter struct {
	TransportMap   map[string]*TransportItem
	TransportSlice []*TransportItem
	Locker         sync.RWMutex
}

type TransportItem struct {
	Trans       *http.Transport
	ServiceName string
}

func NewTransporter() *Transporter {
	return &Transporter{
		TransportMap:   make(map[string]*TransportItem),
		TransportSlice: []*TransportItem{},
		Locker:         sync.RWMutex{},
	}
}

func (t *Transporter) GetTransporter(service *ServiceDetail) (*http.Transport, error) {
	serviceName := service.Info.ServiceName
	// 1.判断是否已经存入数组
	for _, transportItem := range t.TransportSlice {
		if transportItem.ServiceName == serviceName {
			return transportItem.Trans, nil
		}
	}
	// 2.进行默认配置
	if service.LoadBalance.UpstreamConnectTimeout == 0 {
		service.LoadBalance.UpstreamHeaderTimeout = 30
	}
	if service.LoadBalance.UpstreamMaxIdle == 0 {
		service.LoadBalance.UpstreamMaxIdle = 100
	}
	if service.LoadBalance.UpstreamIdleTimeout == 0 {
		service.LoadBalance.UpstreamIdleTimeout = 90
	}
	if service.LoadBalance.UpstreamHeaderTimeout == 0 {
		service.LoadBalance.UpstreamHeaderTimeout = 30
	}

	// 3.生成trans
	trans := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   time.Duration(service.LoadBalance.UpstreamConnectTimeout) * time.Second,
			KeepAlive: 30 * time.Second,
			DualStack: true,
		}).DialContext,
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          service.LoadBalance.UpstreamMaxIdle,
		IdleConnTimeout:       time.Duration(service.LoadBalance.UpstreamIdleTimeout) * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ResponseHeaderTimeout: time.Duration(service.LoadBalance.UpstreamHeaderTimeout) * time.Second,
	}
	transItem := &TransportItem{
		Trans:       trans,
		ServiceName: serviceName,
	}

	// 4.trans 添加到slice
	t.TransportSlice = append(t.TransportSlice, transItem)

	// 5.trans 添加到map
	t.Locker.Lock()
	t.TransportMap[serviceName] = transItem
	defer t.Locker.Unlock()
	return trans, nil
}
